﻿using System.Drawing;
using System.IO;
using SharpGL;
using System.Collections.Generic;
using System;

namespace MutsiEngine
{
    public static class ContentLoader
    {
        private const string CONTENT_PATH = "Content/";
        public static Icon LoadIcon(string path)
        {
            //First check whether the filepath is legit
            if (!File.Exists(CONTENT_PATH + path))
            {
                throw new FileNotFoundException("No file exists at: \"" + path + "\".");
            }

            //Load the file
            FileStream fileStream = new FileStream(CONTENT_PATH + path, FileMode.Open);

            //Create the icon close the stream and return the icon
            Icon icon = new Icon(fileStream);
            fileStream.Close();
            return icon;
        }

        public static uint LoadShader(string path, OpenGL gl, uint ShaderType)
        {
            List<string> shaderStrings = new List<string>();
            // Create an instance of StreamReader to read from a file.
            // The using statement also closes the StreamReader.
            try
            {
                using (StreamReader sr = new StreamReader(CONTENT_PATH + path))
                {
                    string line;
                    // Read and display lines from the file until the end of
                    // the file is reached.
                    while ((line = sr.ReadLine()) != null)
                    {
                        shaderStrings.Add(line.Split('#')[0]);
                    }
                }
            }
            catch (Exception e)
            {
                // Let the user know what went wrong.
                Console.WriteLine("The file could not be read:");
                Console.WriteLine(e.Message);
            }

            //Gather all individual string lenghts and feed them into shaderStringLenghts, this must be passed into the openGL.
            int[] shaderStringLenghts = new int[shaderStrings.Count];
            for (int i = 0; i < shaderStrings.Count; i++)
            {
                shaderStringLenghts[i] = shaderStrings[i].Length;
            }

            uint shader = gl.CreateShader(ShaderType);
            gl.ShaderSource(shader, shaderStrings.Count, shaderStrings.ToArray(), shaderStringLenghts);
            gl.CompileShader(shader);

            return shader;
        }

        public static Mesh LoadMesh(string path)
        {
            List<Vector3> vertices = new List<Vector3>();
            List<Vector2> texCoords = new List<Vector2>();
            List<Vector3> normals = new List<Vector3>();
            List<Vertex> vertexList = new List<Vertex>();

            Mesh mesh = new Mesh();
            // Create an instance of StreamReader to read from a file.
            // The using statement also closes the StreamReader.
            try
            {
                using (StreamReader sr = new StreamReader(CONTENT_PATH + path))
                {
                    // Read and display lines from the file until the end of
                    // the file is reached.
                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        string[] parts = line.Split(' ');
                        if (line.Length > 1)
                        {
                            if (line[0] == 'v' && line[1] == ' ')
                            {
                                vertices.Add(new Vector3(parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])));
                                continue;
                            }

                            if (line[0] == 'v' && line[1] == 't')
                            {
                                texCoords.Add(new Vector2(parseFloat(parts[1]), parseFloat(parts[2])));
                                continue;
                            }
                            if (line[0] == 'v' && line[1] == 'n')
                            {
                                normals.Add(new Vector3(parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])));
                                continue;
                            }

                            if (line[0] == 'f')
                            {
                                for (int i = 1; i < 4; i++)
                                {
                                    string[] face = parts[i].Split('/');

                                    Vertex vertex = new Vertex();
                                    vertex.Position = vertices[int.Parse(face[0])-1];
                                    vertex.TextureCoordinate = texCoords[int.Parse(face[1])-1];
                                    vertex.Normal = normals[int.Parse(face[2])-1];
                                    vertexList.Add(vertex);
                                }
                                continue;
                            }
                        }
                    }
                    mesh.VertexList = vertexList;
                    mesh.Name = path.Split('/')[path.Split('/').Length - 1];
                }
            }
            catch (Exception e)
            {
                // Let the user know what went wrong.
                Console.WriteLine("The file could not be read:");
                Console.WriteLine(e.Message);
            }

            return mesh;
        }

        public static Texture LoadTexture(string path, OpenGL gl)
        {
            uint[] textureArray = new uint[1] { 0 };
            int width, height;
            byte[] pixelData;

            Bitmap image = null;

            //  Create the underlying OpenGL object.
            //  Generate and store a texture identifier.
            gl.GenTextures(1, textureArray);

            //  Try and load the bitmap. Return false on failure.
            try
            {
                image = new Bitmap(path);
            }
            catch (Exception e)
            {
                // Let the user know what went wrong.
                Console.WriteLine("The file could not be read:");
                Console.WriteLine(e.Message);
            }

            //  Get the maximum texture size supported by OpenGL.
            int[] textureMaxSize = { 0 };
            gl.GetInteger(OpenGL.GL_MAX_TEXTURE_SIZE, textureMaxSize);

            //  Find the target width and height sizes, which is just the highest
            //  posible power of two that'll fit into the image.
            int targetWidth = textureMaxSize[0];
            int targetHeight = textureMaxSize[0];

            for (int size = 1; size <= textureMaxSize[0]; size *= 2)
            {
                if (image.Width < size)
                {
                    targetWidth = size / 2;
                    break;
                }
                if (image.Width == size)
                    targetWidth = size;
            }

            for (int size = 1; size <= textureMaxSize[0]; size *= 2)
            {
                if (image.Height < size)
                {
                    targetHeight = size / 2;
                    break;
                }
                if (image.Height == size)
                    targetHeight = size;
            }

            //  If need to scale, do so now.
            if (image.Width != targetWidth || image.Height != targetHeight)
            {
                //  Resize the image.
                Image newImage = image.GetThumbnailImage(targetWidth, targetHeight, null, IntPtr.Zero);

                //  Destory the old image, and reset.
                image.Dispose();
                image = (Bitmap)newImage;
            }

            //  Create an array of pixels the correct size.
            pixelData = new byte[image.Width * image.Height * 4];
            int index = 0;

            //  Go through the pixels backwards, this seems to be how OpenGL wants the data.
            for (int y = image.Height - 1; y >= 0; y--)
            {
                for (int x = 0; x < image.Width; x++)
                {
                    Color pixel = image.GetPixel(x, y);
                    pixelData[index++] = pixel.R;
                    pixelData[index++] = pixel.G;
                    pixelData[index++] = pixel.B;
                    pixelData[index++] = pixel.A;
                }
            }

            //  Set the width and height.
            width = image.Width;
            height = image.Height;

            //  Dispose of the image file.
            image.Dispose();

            gl.BindTexture(OpenGL.GL_TEXTURE_2D, textureArray[0]);
            //  Set the image data.
            gl.TexImage2D(OpenGL.GL_TEXTURE_2D, 0, (int)OpenGL.GL_RGBA,
                    width, height, 0, OpenGL.GL_RGBA, OpenGL.GL_UNSIGNED_BYTE,
                    pixelData);

            //  Set linear filtering mode.
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR);
            gl.BindTexture(OpenGL.GL_TEXTURE_2D, 0);

            Texture texture = new Texture();
            texture.Name = path.Split('/')[path.Split('/').Length - 1];
            texture.TextureAdress = textureArray[0];
            texture.Width = width;
            texture.Height = height;

            return texture;
        }

        public static float parseFloat(String f)
        {
            int len = f.Length;
            float ret = 0f;         // return value
            int pos = 0;          // read pointer position
            int part = 0;          // the current part (int, float and sci parts of the number)
            bool neg = false;      // true if part is a negative number

            // find start
            while (pos < len && (f[pos] < '0' || f[pos] > '9') && f[pos] != '-' && f[pos] != '.')
                pos++;

            // sign
            if (f[pos] == '-')
            {
                neg = true;
                pos++;
            }

            // integer part
            while (pos < len && !(f[pos] > '9' || f[pos] < '0'))
                part = part * 10 + (f[pos++] - '0');
            ret = neg ? (float)(part * -1) : (float)part;

            // float part
            if (pos < len && f[pos] == '.')
            {
                pos++;
                int mul = 1;
                part = 0;
                while (pos < len && !(f[pos] > '9' || f[pos] < '0'))
                {
                    part = part * 10 + (f[pos] - '0');
                    mul *= 10; pos++;
                }
                ret = neg ? ret - (float)part / (float)mul : ret + (float)part / (float)mul;
            }

            // scientific part
            if (pos < len && (f[pos] == 'e' || f[pos] == 'E'))
            {
                pos++;
                neg = (f[pos] == '-'); pos++;
                part = 0;
                while (pos < len && !(f[pos] > '9' || f[pos] < '0'))
                {
                    part = part * 10 + (f[pos++] - '0');
                }
                if (neg)
                    ret = ret / (float)Math.Pow(10, part);
                else
                    ret = ret * (float)Math.Pow(10, part);
            }
            return ret;
        }

    }
}
